home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Developer / StopWatch / Source / Controller.m < prev    next >
Encoding:
Text File  |  1994-02-06  |  17.9 KB  |  854 lines

  1. /*
  2.  * Main controller for Stopwatch app.
  3.  *
  4.  * For legal stuff see the file COPYRIGHT
  5.  */
  6. #import <stdio.h>
  7. #import <ansi/string.h>        /* for import feature only (?) */
  8. #import <bsd/sys/param.h>    /* for MAXPATHLEN */
  9. #import <appkit/NXCType.h>
  10. #import "Controller.h"
  11. #import "StopWatch.h"
  12. #import "InfoPanel.h"
  13. #import "ClientInfo.h"
  14. #import "ClientInspector.h"
  15. #import "SessionEditor.h"
  16. #import "AppIconView.h"
  17. #import "createPath.h"
  18. #import "Preferences.h"
  19.  
  20. #define PRIORITY    NX_MODALRESPTHRESHOLD
  21.  
  22. #define ARCHIVE_FILE    "client.data"
  23. #define TEMPLATE_DIR    "Templates"
  24. #define MAXCLIENTLEN    80
  25. #define SELECTED_CLIENT "SelectedClient"
  26.  
  27. #define VERSION        2    /* the current file version that gets written */
  28. int FileVersion;        /* the version of the file being read */
  29.  
  30. void
  31. freeAndCopy( char **ptr, const char *str )
  32. {
  33.   if ( *ptr )
  34.     free( *ptr );
  35.  
  36.   *ptr = NXCopyStringBuffer(str);
  37. }
  38.  
  39. const char *
  40. currentDate()
  41. {
  42.   time_t now;
  43.   struct tm *tm;
  44.   static char buf[10];
  45.  
  46.   time(&now);
  47.   tm = localtime(&now);
  48.  
  49.   sprintf( buf, "%02d/%02d/%02d", tm->tm_mon + 1, tm->tm_mday, tm->tm_year );
  50.   return buf;
  51. }
  52.  
  53. const char *
  54. currentTime()
  55. {
  56.   time_t now;
  57.   struct tm *tm;
  58.   static char buf[10];
  59.  
  60.   time(&now);
  61.   tm = localtime(&now);
  62.  
  63.   sprintf( buf, "%02d:%02d", tm->tm_hour, tm->tm_min );
  64.   return buf;
  65. }
  66.  
  67. #define MAX_COMMA_STR 50        /* max digits we can handle */
  68.  
  69. void
  70. commafy( int value, char *result )
  71. {
  72.   char *ptr, reverse[MAX_COMMA_STR + 1];
  73.   int digits = 0, signum = 1;
  74.  
  75.   if ( value < 0 ) {
  76.     signum = -1;        /* save the sign (and the whales) */
  77.     value = -value;        /* make it positive for conversion */
  78.   }
  79.  
  80.   ptr = reverse;
  81.  
  82.   do {
  83.     if ( ++digits > 3 ) {
  84.       digits = 1;        /* reset digit counter */
  85.       *ptr++ = ',';        /* insert a comma */
  86.     }
  87.  
  88.     *ptr++ = '0' + value % 10;    /* convert least significant digit to ascii */
  89.     value /= 10;                /* and remove it from the number */
  90.  
  91.   } while ( value != 0 );       /* until no digits remain */
  92.  
  93.   if ( signum < 0 )
  94.     *ptr++ = '-';               /* insert the negative sign */
  95.  
  96.   /* We already have a pointer to the end, so just copy backwards! */
  97.   while ( ptr-- != reverse )
  98.     *result++ = *ptr;
  99.         
  100.   *result = '\0';
  101. }
  102.  
  103. /*
  104.  * Puts commas into a double by convering to an int, after
  105.  * adjusting for rounding and calling commafy()
  106.  */
  107. void
  108. commafyDouble( double value, char *result )
  109. {
  110.   int signum = 1, cents;
  111.   char buf[MAX_COMMA_STR + 1];
  112.  
  113.   if ( value < 0 ) {
  114.     signum = -1;
  115.     value *= -1;
  116.   }
  117.  
  118.   value *= 100;
  119.   value += 0.5;            /* Adjust for rounding */
  120.   cents = (int)value % 100;    /* save the pennies for later */
  121.   value /= 100;            /* chop off last two places */
  122.  
  123.   commafy( (int)(signum < 0 ? -value : value), buf );
  124.   sprintf( result, "%s.%02d", buf, cents );
  125. }
  126.  
  127. /*
  128.  * To avoid having to exec /bin/cp.
  129.  */
  130. int
  131. copyFile( const char *src, const char *dst )
  132. {
  133.   FILE *in, *out;
  134.   int count;
  135.   char buf[BUFSIZ];
  136.  
  137.   if ( ! (in = fopen( src, "r" ) ) ) {
  138.     fprintf( stderr, "Can't open `%s' for reading.\n", src );
  139.     return 0;
  140.   }
  141.  
  142.   if ( ! (out = fopen( dst, "w" ) ) ) {
  143.     fprintf( stderr, "Can't open `%s' for writing.\n", dst );
  144.     fclose(in);
  145.     return 0;
  146.   }
  147.  
  148.   while ( (count = fread( buf, sizeof(char), sizeof(buf), in )) > 0 )
  149.     fwrite( buf, sizeof(char), count, out );
  150.  
  151.   fclose(in);
  152.   fclose(out);
  153.   return 1;
  154. }
  155.  
  156. @interface Controller(PRIVATE)
  157. - (void)selectBrowserRow:(int)row;
  158. - (ClientInfo *)findClient:(const char *)name;
  159. - selectedClient;
  160. - (int)selectedRow;
  161. - (int)compare:obj1 :obj2;    /* comparison method for SortList */
  162. - initInvoice;
  163. - (void)addSession:(const char *)startDate
  164.           time:(const char *)startTime
  165.       duration:(int)minutes
  166.        description:(const char *)desc;
  167. - (void)checkStartButton;
  168. @end
  169.  
  170.  
  171. @implementation Controller
  172.  
  173. DPSTimedEntryProc 
  174. showElapsedTime(DPSTimedEntry teNum, double now, char *data)
  175. {
  176.   [(id)data showElapsedTime];
  177.   return (void *)NULL;
  178. }
  179.  
  180. - (void) removeTimedEntry
  181. {
  182.   if ( teNum ) {
  183.     DPSRemoveTimedEntry(teNum);
  184.     teNum = 0 ;
  185.   }
  186. }
  187.  
  188. - (void) addTimedEntry
  189. {
  190.   [self removeTimedEntry];    /* in case there is one */
  191.   
  192.   /* Set it up so that the clock updates every minute */
  193.   teNum = DPSAddTimedEntry( (double)60.0, (DPSTimedEntryProc)showElapsedTime,
  194.                 (void *)self, PRIORITY );
  195. }
  196.  
  197. - free
  198. {
  199.   [self removeTimedEntry];
  200.   [stopwatch free];
  201.   [infoPanel free];
  202.   return [super free];
  203. }
  204.  
  205. - awakeFromNib
  206. {
  207.   [window setFrameAutosaveName:"Stopwatch"];
  208.  
  209.   /* Make the browser's font match the startButton (can't do this in IB) */
  210.   [[[browser matrixInColumn:0] prototype] setFont:[startButton font]];
  211.  
  212.   return self;
  213. }
  214.  
  215. - add:sender
  216. {
  217.   [[ClientInspector sharedInstance] add:sender];
  218.   return self;
  219. }
  220.  
  221. - modify:sender
  222. {
  223.   [[ClientInspector sharedInstance] modify:sender];
  224.   return self;
  225. }
  226.  
  227. - delete:sender
  228. {
  229.   [[ClientInspector sharedInstance] delete:sender];
  230.   return self;
  231. }
  232.  
  233. - undelete:sender
  234. {
  235.   [[ClientInspector sharedInstance] undelete:sender];
  236.   return self;
  237. }
  238.  
  239. - (void)enableAdd:(BOOL)flag
  240. {
  241.   [addMenuItem setEnabled:flag];
  242. }
  243.  
  244. - (void)enableModify:(BOOL)flag
  245. {
  246.   [modifyMenuItem setEnabled:flag];
  247. }
  248.  
  249. - (void)enableDelete:(BOOL)flag
  250. {
  251.   [deleteButton setEnabled:flag];
  252. }
  253.  
  254. - (void)enableUndelete:(BOOL)flag
  255. {
  256.   [undeleteButton setEnabled:flag];
  257. }
  258.  
  259. /*
  260.  * Redisplay from the data in the clientList. Try to re-select the
  261.  * same item afterwards.
  262.  */
  263. - (void)decacheBrowser
  264. {
  265.   [browser loadColumnZero];
  266.   [self selectBrowserRow:[clientList indexOf:activeClient]];
  267.   [self checkStartButton];
  268. }
  269.  
  270. - (NXTypedStream *)openArchive:(int)mode
  271. {
  272.   NXTypedStream *stream ;  
  273.  
  274.   if ( (stream = NXOpenTypedStreamForFile( filename, mode )) == NULL ) {
  275.     NXRunAlertPanel( [NXApp appName], "Unable to open client data file: `%s'",
  276.             "Create it when needed", NULL, NULL, filename );
  277.     return nil;
  278.   }
  279.   
  280.   return stream;
  281. }
  282.  
  283. /*
  284.  * Read in the client info from the typestream file
  285.  */
  286. - (int)loadClientInfo
  287. {
  288.   NXTypedStream *stream ;
  289.  
  290.   if ( (stream = [self openArchive:NX_READONLY]) == nil )
  291.     return 0;
  292.  
  293.   NXReadType( stream, "i", &FileVersion );
  294.   [clientList read:stream];
  295.   [clientList sort];
  296.   NXCloseTypedStream(stream) ;
  297.  
  298.   return 1;
  299. }
  300.  
  301. - (int)saveClientInfoToStream:(NXTypedStream *)stream
  302. {
  303.   int version = VERSION;
  304.   
  305.   NXWriteType( stream, "i", &version );
  306.   [clientList write:stream];
  307.   return 1;
  308. }
  309.  
  310. - (int)saveClientInfo
  311. {
  312.   NXTypedStream *stream ;
  313.   char backup[FILENAME_MAX + 1];
  314.  
  315.   /*
  316.    * If this is the first write, move the old filename to
  317.    * filename~ to serve as a backup.
  318.    */
  319.   if ( didBackup == NO ) {
  320.     sprintf( backup, "%s~", filename );
  321.     rename( filename, backup );
  322.     didBackup = YES;
  323.   }
  324.  
  325.   if ( (stream = [self openArchive:NX_WRITEONLY]) == nil )
  326.     return 0;
  327.  
  328.   [self saveClientInfoToStream:stream];
  329.   NXCloseTypedStream(stream);
  330.   return 1;
  331. }
  332.  
  333. /*
  334.  * Edit the selected invoicing template by messaging to the
  335.  * workspace to open the corresponding file. Sender is the
  336.  * Matrix containing the menu of template names.
  337.  */
  338. - editTemplate:sender
  339. {
  340.   id cell = [sender cellAt:[sender selectedRow] :0];
  341.  
  342.   [self initInvoice];
  343.   [invoice editTemplate:[cell title]];
  344.   return self;
  345. }
  346.  
  347. - preferences:sender
  348. {
  349.   [preferences display];
  350.   return self;
  351. }
  352.  
  353. - saveAs:sender
  354. {
  355.   SavePanel *savePanel = [SavePanel new];
  356.   NXTypedStream *stream;
  357.   const char *path;
  358.  
  359.   if ( [savePanel runModalForDirectory:dirname file:""] == 0 )
  360.     return nil;
  361.  
  362.   path = [savePanel filename];
  363.  
  364.   if ( (stream = NXOpenTypedStreamForFile( path, NX_WRITEONLY )) == NULL ) {
  365.     NXRunAlertPanel( [NXApp appName], "Unable to open file for writing: `%s'",
  366.             "What the...?", NULL, NULL, path );
  367.     return nil;
  368.   }
  369.   
  370.   [self saveClientInfoToStream:stream];
  371.   NXCloseTypedStream(stream);
  372.   
  373.   return self;
  374. }
  375.  
  376. - clientList
  377. {
  378.   return clientList;
  379. }
  380.  
  381. /*
  382.  * Select the client who was saved in the defaults db on our last exit.
  383.  */
  384. - (void)selectPreviousClient
  385. {
  386.   const char *name = NXGetDefaultValue( [NXApp appName], SELECTED_CLIENT );
  387.   ClientInfo *client;
  388.  
  389.   if ( name && *name ) {
  390.     client = [self findClient:name];
  391.     [self selectBrowserRow:[clientList indexOf:client]];
  392.     [self selectClient:self];
  393.   }
  394. }
  395.  
  396. - appDidInit:sender
  397. {
  398.   NXRect rect = {{0.0, 0.0}, {64.0, 64.0}};
  399.  
  400.   if ( createPath( dirname, DIRMODE ) != PathCreationOk ) {
  401.     NXRunAlertPanel( [NXApp appName], "Cannot create path `%s'",
  402.             "Damned UNIX!", NULL, NULL, dirname );
  403.     [NXApp terminate:sender];
  404.   }
  405.  
  406.   preferences = [Preferences new];
  407.  
  408.   [self loadClientInfo];
  409.   [self decacheBrowser];
  410.   [self selectPreviousClient];
  411.  
  412.   if ( [preferences hideOnAutoLaunch] )
  413.     [NXApp hide:self];
  414.   else
  415.     [window makeKeyAndOrderFront:self];
  416.  
  417.   /* make view that tracks elapsedTime be the appIcon window's contentView */
  418.   appIconView = [[AppIconView alloc] initFrame:&rect 
  419.                    sourceView:elapsedTimeField];
  420.   [[[NXApp appIcon] setContentView:appIconView] free];
  421.  
  422.   [browser setDoubleAction:@selector(inspect:)];
  423.   [browser setTarget:self];
  424.  
  425.   /* If there are no clients defined yet, disable the start buttons */
  426.   [self checkStartButton];
  427.  
  428.   return self;
  429. }
  430.  
  431. /*
  432.  * If we logout, or there's a powerOff,  make sure the time gets saved.
  433.  */
  434. - app:sender powerOffIn:(int)ms andSave:(int)aFlag
  435. {
  436.   return [self appWillTerminate:sender];
  437. }
  438.  
  439. - appDidUnhide:sender
  440. {
  441.   [window makeKeyAndOrderFront:self];
  442.   return self;
  443. }
  444.  
  445. - appWillTerminate:sender
  446. {
  447.   ClientInfo *client = [self selectedClient];
  448.  
  449.   if ( teNum )
  450.     [self stopClock];
  451.         
  452.   NXWriteDefault([NXApp appName], SELECTED_CLIENT, client ? [client shortName] : "");
  453.  
  454.   return self;
  455. }
  456.  
  457. - init
  458. {
  459.   char path[FILENAME_MAX + 1];
  460.  
  461.   [super init];
  462.  
  463.   stopwatch = [[StopWatch alloc] init];
  464.  
  465.   clientList = [[SortList alloc] init];
  466.   [clientList setAutoSort:YES];
  467.   [clientList setDelegate:self];
  468.  
  469.   sprintf( path, "%s/Library/%s", NXHomeDirectory(), [NXApp appName] );
  470.   dirname = NXCopyStringBuffer(path);
  471.  
  472.   sprintf( path, "%s/%s", dirname, ARCHIVE_FILE );
  473.   filename = NXCopyStringBuffer(path);
  474.  
  475.   return self;
  476. }
  477.  
  478. - (const char *) description
  479. {
  480.   return [description stringValue];
  481. }
  482.  
  483. /*
  484.  * Called once per minute by the timed entry routine while the clock is running.
  485.  */
  486. - showElapsedTime
  487. {
  488.   [elapsedTimeField setStringValue:[stopwatch elapsedTime]];
  489.   [appIconView display];
  490.   return self;
  491. }
  492.  
  493. /*
  494.  * Respond to the user's selection of a client
  495.  */
  496. - selectClient:sender
  497. {
  498.   /* Assume that this means we should stop the previous client */
  499.   if ( [stopwatch running] == YES )
  500.     [startButton performClick:sender];
  501.  
  502.   activeClient = [self selectedClient];
  503.   [description setStringValue:[activeClient lastDescription]];
  504.   [description selectText:sender];
  505.   return self;
  506. }
  507.  
  508. /*
  509.  * The start button highlights, but we need to force the title to "Stop".
  510.  * Setting the Alternate Title didn't seem to do the right thing in IB.
  511.  */
  512. - startClock
  513. {
  514.   id font = [elapsedTimeField font];
  515.  
  516.   [elapsedTimeField setFont:[[FontManager new] convertWeight:YES of:font]];
  517.   [self addTimedEntry];
  518.   [startButton   setTitle:"Stop"];
  519.   [startMenuItem setTitle:"Stop"];
  520.   [stopwatch startWatch];
  521.   [self showElapsedTime];
  522.   activeClient = [self selectedClient];
  523.   return self;
  524. }
  525.  
  526. /*
  527.  * The mirror image of the above routine
  528.  */
  529. - stopClock
  530. {
  531.   id font = [elapsedTimeField font];
  532.   [elapsedTimeField setFont:[[FontManager new] convertWeight:NO of:font]];
  533.   [self removeTimedEntry];
  534.   [startButton   setTitle:"Start"];
  535.   [startMenuItem setTitle:"Start"];
  536.   [stopwatch stopWatch];
  537.   [self showElapsedTime];
  538.   [self addSession:[stopwatch startDateString]
  539.            time:[stopwatch startTimeString]
  540.       duration:[stopwatch elapsedMinutes]
  541.        description:[self description]];
  542.   activeClient = nil;
  543.   return self;
  544. }
  545.  
  546. /*
  547.  * Called whenever the startButton is pressed.
  548.  */
  549. - buttonHandler:sender
  550. {
  551.   if ( [startButton state] == 1 )
  552.     [self startClock];
  553.   else
  554.     [self stopClock];
  555.  
  556.   return self;
  557. }
  558.  
  559. - showInfo:sender
  560. {
  561.   [[InfoPanel new] showInfo];
  562.   return self;
  563. }
  564.  
  565. /*
  566.  * Inspect the currently selected client
  567.  */
  568. - inspect:sender
  569. {
  570.   Matrix *matrix = [browser matrixInColumn:0];
  571.   ClientInspector *inspector = [ClientInspector sharedInstance];
  572.  
  573.   [inspector selectClientAt:[matrix selectedRow]];
  574.   [inspector display];
  575.   return self;
  576. }
  577.  
  578. - inspectSessions:sender
  579. {
  580.   [[ClientInspector sharedInstance] showHours:sender];
  581.   return self;
  582. }
  583.  
  584. - inspectExpenses:sender
  585. {
  586.   [[ClientInspector sharedInstance] showExpenses:sender];
  587.   return self;
  588. }
  589.  
  590. - inspectClients:sender
  591. {
  592.   [[ClientInspector sharedInstance] showClient:sender];
  593.   return self;
  594. }
  595.  
  596. - generateDetail:sender
  597. {
  598.   [self initInvoice];
  599.   [invoice generate:clientList];
  600.   return self;
  601. }
  602.  
  603. /*
  604.  * Compact consecutive sessions with identical descriptions into
  605.  * a single session with the same total time.
  606.  */
  607. - compactClients:sender
  608. {
  609.   int i, count = [clientList count];
  610.  
  611.   for ( i = 0; i < count; i++ )
  612.     [[clientList objectAt:i] compactSessions];
  613.  
  614.   /* If it's showing, redisplay it, otherwise do nothing */
  615.   [[ClientInspector sharedInstance] decacheView];
  616.  
  617.   [self saveClientInfo];
  618.   return self;
  619. }
  620.  
  621. /*
  622.  * This needs to be cleaned up...
  623.  */
  624. - import:sender
  625. {
  626.   FILE *fp;
  627.   const char *pathname;
  628.   char buf[512], *tok;
  629.   char shortName[80], startDate[80], startTime[80], minutes[80], desc[256];
  630.   id openPanel = [OpenPanel new];
  631.   ClientInspector *inspector = [ClientInspector sharedInstance];
  632.   char delimiter[2], endDelimiters[10];
  633.  
  634.   if ( [openPanel runModal] == 0 )
  635.     return nil;
  636.  
  637.   pathname = [openPanel filename];
  638.  
  639.   if ( ! (fp = fopen( pathname, "r" ) ) ) {
  640.     NXRunAlertPanel( [NXApp appName], "Unable to open import file: `%s'",
  641.             "Eat me!", NULL, NULL, pathname );
  642.     return self;
  643.   }
  644.   
  645.   sprintf( delimiter,     "%c",   DELIMITER );
  646.   sprintf( endDelimiters, "%c\n", DELIMITER );
  647.  
  648.   while ( fgets(buf, sizeof(buf), fp) ) {
  649.     ClientInfo *info;
  650.     Session *session;
  651.  
  652.     tok = strtok( buf, delimiter );
  653.     strcpy( shortName, tok ) ;
  654.  
  655.     if ( ! (info = [self findClient:shortName]) ) {
  656.       NXRunAlertPanel( [NXApp appName], "Ignoring unknown client: `%s'",
  657.               "Who needs 'em?", NULL, NULL, shortName );
  658.       continue;
  659.     }
  660.  
  661.     tok = strtok( NULL, delimiter );
  662.     strcpy( startDate, tok );
  663.  
  664.     tok = strtok( NULL, delimiter );
  665.     strcpy( startTime, tok );
  666.  
  667.     tok = strtok( NULL, delimiter );
  668.     strcpy( minutes, tok );
  669.  
  670.     tok = strtok( NULL, endDelimiters ); /* throw out newline too.  */
  671.     strcpy( desc, tok );
  672.  
  673.     session = [[Session alloc]
  674.            init:startDate time:startTime
  675.            duration:atoi(minutes) description:desc];
  676.  
  677.     [info addSession:session];
  678.     [inspector updatedInfo:info];
  679.   }
  680.  
  681.   fclose(fp);
  682.   [self saveClientInfo];
  683.   return self;
  684. }
  685.  
  686. - export:sender
  687. {
  688.   FILE *fp;
  689.   const char *pathname;
  690.   int i, count = [clientList count];
  691.   id savePanel = [SavePanel new];
  692.  
  693.   if ( [savePanel runModal] == 0 )
  694.     return nil;
  695.  
  696.   pathname = [savePanel filename];
  697.  
  698.   if ( ! (fp = fopen( pathname, "w" ) ) ) {
  699.     NXRunAlertPanel( [NXApp appName], "Unable to open export file: `%s'",
  700.             "I'll be darned!", NULL, NULL, pathname );
  701.     return self;
  702.   }
  703.   
  704.   for ( i = 0; i < count; i++ )
  705.     [[clientList objectAt:i] exportToFile:fp];
  706.  
  707.   fclose(fp);
  708.   return self;
  709. }
  710.  
  711. /*
  712.  * Clear out all session information from all clients.
  713.  */
  714. - closeMonth:sender
  715. {
  716.   ClientInspector *inspector = [ClientInspector sharedInstance];
  717.  
  718.   /* Give the user a chance to change their mind... */
  719.   if ( NXRunAlertPanel( [NXApp appName], "Delete all session and expense data?",
  720.            "Delete all data", "Hell no!", NULL ) == NX_ALERTDEFAULT ) {
  721.     [clientList makeObjectsPerform:@selector(deleteSessionsAndExpenses)];
  722.     [inspector closeMonth];
  723.     [self saveClientInfo];    /* write the newly empty file */
  724.     [inspector decacheView];
  725.   }
  726.  
  727.   return self;
  728. }
  729.  
  730. @implementation Controller(PRIVATE)
  731.  
  732. - (void)selectBrowserRow:(int)row
  733. {
  734.   Matrix *matrix = [browser matrixInColumn:0];
  735.  
  736.   if ( row < 0 )
  737.     row = 0;
  738.  
  739.   [matrix selectCellAt:row :0];
  740.   [matrix scrollCellToVisible:row :0];  
  741. }
  742.  
  743. /*
  744.  * Find a client by short name
  745.  */
  746. - (ClientInfo *)findClient:(const char *)name
  747. {
  748.   int i, count = [clientList count];
  749.  
  750.   for ( i = 0; i < count; i++ ) {
  751.     ClientInfo *info;
  752.  
  753.     info = [clientList objectAt:i];
  754.     if ( strcmp( name, [info shortName] ) == 0 )
  755.       return info ;
  756.   }
  757.   return nil;
  758. }
  759.  
  760. - initInvoice
  761. {
  762.   char path[FILENAME_MAX + 1];
  763.  
  764.   if ( invoice == nil ) {
  765.     sprintf( path, "%s/%s", dirname, TEMPLATE_DIR );
  766.     invoice = [[Invoice alloc] initTemplateDir:path];
  767.   }
  768.   return invoice;
  769. }
  770.  
  771. - (int)selectedRow
  772. {
  773.   return [[browser matrixInColumn:0] selectedRow];
  774. }
  775.  
  776. - selectedClient
  777. {
  778.   return [clientList objectAt:[self selectedRow]];
  779. }
  780.  
  781. /*
  782.  * Make sure the start buttons are disabled if there are
  783.  * no clients defined, and enabled if there are.
  784.  */
  785. - (void)checkStartButton
  786. {
  787.   BOOL flag = ( [clientList count] ? YES : NO );
  788.  
  789.   [startMenuItem setEnabled:flag];
  790.   [startButton   setEnabled:flag];
  791.  
  792.   /* the same goes for these */
  793.   [sessionMenuItem setEnabled:flag];
  794.   [expenseMenuItem setEnabled:flag];
  795.  
  796.   /*
  797.    * The only reasonable thing to do if there are no
  798.    * clients is to create some!
  799.    */
  800.   if ( flag == NO )
  801.     [self inspectClients:nil];
  802. }
  803.  
  804. /*
  805.  * Create a new session object and add it to the proper client's
  806.  * ClientInfo list. Tell the browser what happened so it can update.
  807.  */
  808. - (void)addSession:(const char *)startDate
  809.           time:(const char *)startTime
  810.       duration:(int)minutes
  811.        description:(const char *)desc
  812. {
  813.   Session *session = [[Session alloc]
  814.                 init:startDate time:startTime
  815.                    duration:minutes description:desc];
  816.   [activeClient addSession:session];
  817.   [[ClientInspector sharedInstance] updatedInfo:activeClient];
  818.   [self saveClientInfo];
  819. }
  820.  
  821. /*
  822.  * Compare two ClientInfo objects. Sort alpha by long name.
  823.  */
  824. - (int)compare:obj1 :obj2
  825. {
  826.   return strcmp( [obj1 clientName], [obj2 clientName] );
  827. }
  828.  
  829. /*
  830.  * Delegated method of NXBrowser.  This should be consolidated into a single
  831.  * object.  Right now this method appears (almost) identically in the Controller
  832.  * and the ClientMgr...  (Here we use the shortName instead of the full one.)
  833.  */
  834. - (int) browser:sender fillMatrix:matrix inColumn:(int)column
  835. {
  836.   int i, count = [clientList count];
  837.   
  838.   for ( i = 0; i < count; i++ ) {
  839.     const char *name;
  840.     id cell;
  841.     
  842.     [matrix addRow];
  843.     name = [[clientList objectAt:i] shortName];
  844.     cell = [matrix cellAt:i :0];    /* 1 dimen. matrix: always use col 0! */
  845.     [cell setStringValue:name];
  846.     [cell setLoaded:YES];
  847.     [cell setLeaf:YES];
  848.   }
  849.  
  850.   return count ;
  851. }
  852.  
  853. @end
  854.